#include "TaskTime.h"
#include "common.h"
#include <sys/eventfd.h>

typedef std::lock_guard<std::mutex> locker;

bool _large(const struct timeval& t1, const struct timeval& t2)
{
    if (t1.tv_sec != t2.tv_sec) return t1.tv_sec > t2.tv_sec;

    return t1.tv_usec >= t2.tv_usec;
}

TaskTimer* TaskTimer::m_instance = NULL;

TaskTimer::TaskTimer():
    m_quit(0)
{
    m_timeSignal = eventfd(0, 0);
    int err = pthread_spin_init(&m_spin_lock, PTHREAD_PROCESS_PRIVATE);
    if (0 != err)
    {
        LOG2(ERROR,
            "Error on pthread_spin_init , info : %s",
            strerror(err)
        );
    }
}

TaskTimer::~TaskTimer()
{
    pthread_spin_destroy(&m_spin_lock);
}

TaskTimer* TaskTimer::Instance()
{
    static std::once_flag oc;

    std::call_once(oc, [&]()
        {
            m_instance = new TaskTimer();
        }
    );
    return m_instance;
}

int TaskTimer::shutdown()
{
    uint64_t u = 1;
    m_quit = 1;

    if (sizeof(uint64_t) != write(m_timeSignal, &u, sizeof(uint64_t)))
    {
        LOG2(ERROR, "AddTimeoutTask error, %s", strerror(errno));
        return -1;
    }
    return 0;
}

void TaskTimer::HandleTimeoutTask()
{
    std::weak_ptr<Msg> param;
    std::function<void(std::weak_ptr<Msg>)> func;

    uint64_t exitFlag = 0;
    int ret = 0;
    fd_set fdEvent;
    struct timeval tempTime, curTime, waitTime;

    while (!m_quit)
    {
        gettimeofday(&curTime, NULL);

        FD_ZERO(&fdEvent);
        FD_SET(m_timeSignal, &fdEvent);

        {
            if (0 != spin_lock())
            {
                continue;
            }

            auto iter = m_TimeTask.begin();
            if (iter == m_TimeTask.end())
            {
                waitTime.tv_sec = waitTime.tv_usec = 100;
            }
            else if (!_large(curTime, iter->first))
            {
                tempTime = iter->first;

                if (curTime.tv_usec > tempTime.tv_usec)
                {
                    tempTime.tv_sec -= 1;
                    waitTime.tv_usec = tempTime.tv_usec - curTime.tv_usec + 1000000;
                }
                waitTime.tv_sec = tempTime.tv_sec - curTime.tv_sec;
            }
            else
            {
                func = iter->second->m_func;
                param = iter->second->m_param;
                
                m_TimeTask.erase(iter); unlock();

                func(param);
                continue;
            }
            unlock();
        }

    again_select:
        ret = select(m_timeSignal + 1, &fdEvent, NULL, NULL, &waitTime);/* wait work signal */
        if (ret < 0)
        {
            if (errno == EINTR)
                goto again_select;

            break;
        }

        if (FD_ISSET(m_timeSignal, &fdEvent))
        {
            if (sizeof(uint64_t) != read(m_timeSignal, &exitFlag, sizeof(uint64_t)))
            {
                break;
            }
        }
    }
}

int TaskTimer::AddTimeoutTask(
    struct timeval times, std::function<void(std::weak_ptr<Msg>)> m_func, std::weak_ptr<Msg> m_param
)
{
    if (0 != spin_lock()) return 0;

    struct timeval curTime = { 0, 0 }, tempVal;
    gettimeofday(&curTime, NULL);

    tempVal = curTime;

    curTime.tv_sec += times.tv_sec;
    curTime.tv_usec = curTime.tv_usec + times.tv_usec;

    if (curTime.tv_usec >= 1000000)
    {
        curTime.tv_usec -= 1000000;
        curTime.tv_sec += 1;
    }
    m_TimeTask.insert({ curTime, TimePackerPtr(new TimePacker(m_param, m_func)) });

    uint64_t u = 1, ret = 0;
    if (sizeof(uint64_t) != write(m_timeSignal, &u, sizeof(uint64_t)))
    {
        LOG2(ERROR, "AddTimeoutTask error, %s", strerror(errno));
        ret = 0;
    }
    unlock();

    return ret;
}

int TaskTimer::spin_lock()
{
    int err = pthread_spin_lock(&m_spin_lock);
    if (0 != err)
    {
        LOG2(ERROR,
            "Error on pthread_spin_lock, info : %s",
            strerror(err)
        );
        return -1;
    }
    return 0;
}

int TaskTimer::unlock()
{
    int err = pthread_spin_unlock(&m_spin_lock);
    if (0 != err)
    {
        LOG2(ERROR,
            "Error on pthread_spin_lock, info : %s",
            strerror(err)
        );
        return -1;
    }
    return 0;
}